package erfgame.core.world;

import java.util.logging.Level;
import java.util.logging.Logger;

import erfgame.core.event.Event;
import erfgame.core.event.EventQueue;
import erfgame.core.event.EventQueueListener;

public class WorldEventQueueRunner implements Runnable, EventQueueListener {
	
	private static final Logger log = Logger.getLogger( WorldEventQueueRunner.class.getName() );
	
	private World world;
	private EventQueue eventQueue;
	private boolean running;
	private boolean isStopped;
	private Object stopWait = new Object();
	
	public WorldEventQueueRunner( World world ) {
		this.world = world;
		this.eventQueue = world.getEventQueue();
		this.eventQueue.addEventQueueListener( this );
	}

	public void run() {
		this.running = true;
		outer: while( running ) {
			synchronized( this ) {
				if( this.eventQueue.isEmpty() ) {
					try {
						this.wait();
					} catch( InterruptedException ex ) {
						log.log( Level.INFO, "thread interrupted", ex );
					}
				}
			}
			long startTime = System.currentTimeMillis();
			long worldTime = world.getWorldTime();
			long cpuTime = 0;
			long timeDiff = 0;
			Event event;
			do {
				event = eventQueue.peek();
				if( event == null ) {
					continue outer;
				}
				long eventTime = event.getTime();
				timeDiff = eventTime - worldTime;
				cpuTime = System.currentTimeMillis() - startTime;
				long waitTime = timeDiff - cpuTime;
				if( waitTime > 0 ) {
					synchronized( this ) {
						if( running ) {
							try {
								this.wait( waitTime );
							} catch( InterruptedException ex ) {
								log.log( Level.INFO, "thread interrupted", ex );
							}
						}
					}
					if( !running ) {
						break outer;
					}
				}
			} while( event != eventQueue.peek() );
			
			try {
				// can't use this, the event queue might have changed since we exited the queue (which means our times are going to be slightly out of order)
				//event = eventQueue.pop();
				eventQueue.removeEvent(event);
				world.setWorldTime( event.getTime() );
				event.getAction().perform();
			} catch( Exception ex ) {
				log.log( Level.SEVERE, "unable to perform action "+event.getAction(), ex );
			}
		}
		synchronized( this.stopWait ) {
			this.isStopped = true;
			this.stopWait.notify();
		}
	}
	
	/**
	 * Synchronous stop operation
	 */
	public void stop() {
		synchronized( this ) {
			if( running ) {
				this.running = false;
				this.notify();
			}
		}
		synchronized( this.stopWait ) {
			if( !this.isStopped ) {
				try {
					this.stopWait.wait();
				} catch( InterruptedException ex ) {
					log.log( Level.INFO, "waiting interrupted", ex );
				}
			}
		}
	}

	public void headChanged(Event newHead) {
		synchronized( this ) {
			this.notify();
		}
	}
}
